﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace ElectronicCommerce.Framework.TcpHelper.Server
{
    /// <summary>
    /// Message Server
    /// </summary>
    public class MessageServer : IMessageServer
    {
        #region Fields

        private TcpListener _seaver;

        private bool _isStopped = false;

        private IMessageSerializer _messageSerializer;

        private readonly MessageClientStore _clientStore = new MessageClientStore();


        public event EventHandler<ClientEventArgs> ClientConnected;

        public event EventHandler<ClientEventArgs> ClientDisconnected;

        public event EventHandler<MessageEventArgs> MessageReseive;

        #endregion

        #region Constructors

        private MessageServer() { }

        public MessageServer(string ip, int port, IMessageSerializer messageSerializer)
            : this(IPAddress.Parse(ip), port, messageSerializer)
        { }

        public MessageServer(IPAddress address, int port, IMessageSerializer messageSerializer)
        {
            _seaver = new TcpListener(address, port);
            this._messageSerializer = messageSerializer;
        }

        #endregion

        #region Methods

        public void Start()
        {
            try
            {
                _seaver.Start();
            }
            catch
            {
                throw;
            }
            _seaver.BeginAcceptTcpClient(new AsyncCallback(OnAcceptTcpClient), null);
        }

        public void Stop()
        {
            if (_seaver != null)
            {
                _isStopped = true;
                foreach (var clientInfo in _clientStore.GetClientInfos())
                {
                    CloseClient(clientInfo);
                }
                _seaver.Stop();
            }
        }

        public void SendMessage(Guid clientId, MessageInfo message)
        {
            var clientInfo = _clientStore.GetClientInfoById(clientId);
            if (clientInfo != null)
            {
                var args = new SocketAsyncEventArgs();

                #region Convert data

                byte[] serializedMessage = this._messageSerializer.Serialize(message);

                List<byte> messageByteList = serializedMessage.ToList();

                for (int i = 1023; i < serializedMessage.Length; i += 1023)
                {
                    messageByteList.Insert(i, 0);
                    i++;
                }

                messageByteList.Add(1);

                args.SetBuffer(messageByteList.ToArray(), 0, messageByteList.Count);

                #endregion

                try
                {
                    clientInfo.TcpClient.Client.SendAsync(args);
                }
                catch (Exception)
                {
                    this._clientStore.RemoveClient(clientInfo.Id);
                    CloseClient(clientInfo);
                    this.OnClientDisconnected(clientInfo.Id);
                }
            }
        }

        private void OnAcceptTcpClient(IAsyncResult ar)
        {
            if (_isStopped)
            {
                return;
            }

            var clientInfo = new ClientInfo(this._seaver.EndAcceptTcpClient(ar));

            BeginRecievedMessage(new ClientMessageInfo()
            {
                ClientInfo = clientInfo,
                MessagePackage = new byte[1024],
                SerializeMessage = new List<byte>()
            });

            _clientStore.AddClient(clientInfo);

            OnClientConnected(clientInfo.Id);

            _seaver.BeginAcceptTcpClient(new AsyncCallback(OnAcceptTcpClient), null);
        }

        private void BeginRecievedMessage(ClientMessageInfo clientMessageInfo)
        {
            try
            {
                clientMessageInfo.ClientInfo.TcpClient.Client
                    .BeginReceive(clientMessageInfo.MessagePackage,
                    0, clientMessageInfo.MessagePackage.Length,
                    SocketFlags.None, new AsyncCallback(ClientMessageReceived),
                    clientMessageInfo);
            }
            catch (SocketException ex)
            {
                throw ex;
            }
        }

        private void ClientMessageReceived(IAsyncResult ar)
        {
            var state = (ClientMessageInfo)ar.AsyncState;

            try
            {
                int bytes = state.ClientInfo.TcpClient.Client.EndReceive(ar);

                if (bytes > 0)
                {
                    state.SerializeMessage.AddRange(state.MessagePackage.Take(bytes - 1));

                    if (state.MessagePackage[bytes - 1] > 0)
                    {
                        this.OnMessageReseived(state.ClientInfo.Id, state.SerializeMessage.ToArray());
                        state.SerializeMessage.Clear();
                    }
                }
            }
            catch
            {
                _clientStore.RemoveClient(state.ClientInfo.Id);
                this.OnClientDisconnected(state.ClientInfo.Id);
            }
        }

        public void CloseClient(Guid id)
        {
            CloseClient(_clientStore.GetClientInfoById(id));
        }

        private void CloseClient(ClientInfo clientInfo)
        {
            if (clientInfo != null)
            {
                if (clientInfo.TcpClient.GetStream() != null)
                {
                    clientInfo.TcpClient.GetStream().Close();
                }
                clientInfo.TcpClient.Close();
            }
        }

        #endregion

        #region Events

        private void OnClientConnected(Guid clientId)
        {
            if (this.ClientConnected != null && _clientStore.ContainsClient(clientId))
            {
                this.ClientConnected(_clientStore.GetClientInfoById(clientId), new ClientEventArgs(clientId));
            }
        }

        private void OnClientDisconnected(Guid clientId)
        {
            if (this.ClientDisconnected != null && _clientStore.ContainsClient(clientId))
            {
                this.ClientDisconnected(_clientStore.GetClientInfoById(clientId), new ClientEventArgs(clientId));
            }
        }

        private void OnMessageReseived(Guid clientId, byte[] serializedMessage)
        {
            if (this.MessageReseive != null && _clientStore.ContainsClient(clientId))
            {
                this.MessageReseive(_clientStore.GetClientInfoById(clientId),
                    new MessageEventArgs(clientId, this._messageSerializer.Deserialize(serializedMessage)));
            }
        }

        #endregion

        #region Inner class

        private class ClientMessageInfo
        {
            public ClientInfo ClientInfo { get; set; }

            public byte[] MessagePackage { get; set; }

            public List<byte> SerializeMessage { get; set; }
        }

        #endregion

        #region IMessageServer Members

        void IMessageServer.Start()
        {
            this.Start();
        }

        void IMessageServer.Stop()
        {
            this.Stop();
        }

        void IMessageServer.SendMessage(Guid clientId, MessageInfo message)
        {
            this.SendMessage(clientId, message);
        }

        #endregion
    }
}
